﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery.Toolkit
{
    public static class QueryChecker
    {
        public static void CheckNonQueries(string connectionString, params Func<QueryContext, INonQuery>[] queryFuncs)
        {
            foreach (var queryFunc in queryFuncs)
            {
                ExecuteQuery(connectionString, queryFunc, delegate { return new {}; }, false);
            }
        }

        public static void CheckQueries<T>(string connectionString, QueryResultPropertyTypeChecker queryResultPropertyTypeChecker, params Func<QueryContext, IQuery<T>>[] queryFuncs)
        {
            foreach (var queryFunc in queryFuncs)
            {
                var dataInfoPropertyInfos = typeof(T).GetProperties();
                var dataInfoPropertyInfoDictionary = dataInfoPropertyInfos.ToDictionary(
                    propertyInfo => propertyInfo.Name);
                var executeResult = ExecuteQuery(
                    connectionString, queryFunc,
                    (sqlDataReader, query) => new {schemaTable = sqlDataReader.GetSchemaTable(), query}, true);
                executeResult.schemaTable.Rows.Cast<DataRow>().ForEach(
                    dataRow => dataInfoPropertyInfoDictionary.GetOptionalValue(dataRow.GetColumnName()).Process(
                        propertyInfo => queryResultPropertyTypeChecker(
                            propertyInfo, dataRow, () => GetQueryType(executeResult.query)),
                        () =>
                            {
                                throw new QueryCheckException(
                                    string.Format(
                                        @"The query result type does not contain a definition for property '{0}'.
QueryType = '{1}'",
                                        dataRow.GetColumnName(),
                                        GetQueryType(executeResult.query)));
                            }
                        ));
                var schemaTableDictionary = executeResult.schemaTable.Rows.Cast<DataRow>().ToDictionary(_ => _.GetColumnName());
                foreach (var propertyInfo in dataInfoPropertyInfos)
                {
                    if (!schemaTableDictionary.GetOptionalValue(propertyInfo.Name).HasValue)
                    {
                        throw new QueryCheckException(
                            string.Format(
                                @"The query result does not contain a column '{0}'.
QueryType = '{1}'",
                                propertyInfo.Name,
                                GetQueryType(executeResult.query)));
                    }
                }
            }
        }

        private static TResult ExecuteQuery<TResult>(string connectionString, Func<QueryContext, IQuery> queryFunc, Func<SqlDataReader, IQuery, TResult> resultFunc, bool doubleCheck)
        {
            return connectionString.WithSqlConnection(
                connection => connection.WithSqlCommand(
                    sqlCommand =>
                        {
                            var query = queryFunc(new QueryContext(sqlCommand));
                            var queryText = query.TransformText();
                            sqlCommand.CommandText = doubleCheck ? InsertFalseCondition(queryText) : queryText;
                            Console.WriteLine(queryText);

                            SqlDataReader sqlDataReader;
                            try
                            {
                                sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
                            }
                            catch (SqlException sqlException)
                            {
                                if (sqlCommand.CommandText != queryText)
                                {
                                    sqlCommand.CommandText = queryText;
                                    SqlDataReader sqlDataReader2;
                                    try
                                    {
                                        sqlDataReader2 = sqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
                                    }
                                    catch (SqlException sqlException2)
                                    {
                                        throw new QueryCheckException(sqlCommand.CommandText, sqlException2, GetQueryType(query));
                                    }
                                    using (sqlDataReader2)
                                    {
                                        return resultFunc(sqlDataReader2, query);
                                    }
                                }
                                throw new QueryCheckException(sqlCommand.CommandText, sqlException, GetQueryType(query));
                            }
                            using (sqlDataReader)
                            {
                                return resultFunc(sqlDataReader, query);
                            }
                        })
                );
        }

        private static string InsertFalseCondition(string queryText)
        {
            var parser = new TSql100Parser(false);
            IList<ParseError> parseErrors;
            var scriptFragment = parser.Parse(new StringReader(queryText), out parseErrors);
            if (!(scriptFragment is TSqlScript))
                return queryText;
            var actions = new List<Action<StringBuilder>>();
            foreach (var sqlBatch in ((TSqlScript)scriptFragment).Batches)
            {
                foreach (var sqlStatement in sqlBatch.Statements)
                {
                    if (!(sqlStatement is SelectStatement)) continue;
                    var selectStatement = ((SelectStatement)sqlStatement).QueryExpression;
                    if (!(selectStatement is QuerySpecification)) continue;
                    var querySpecification = (QuerySpecification)selectStatement;
                    var whereClause = querySpecification.WhereClause;
                    if (whereClause == null)
                    {
                        var fromClause = querySpecification.FromClauses.LastOrDefault();
                        if (fromClause == null)
                            continue;
                        actions.Add(builder => builder.Insert(
                            fromClause.StartOffset + fromClause.FragmentLength, " WHERE 0 = 1 "));
                    }
                    else
                    {
                        var sqlParserToken = whereClause.ScriptTokenStream[whereClause.FirstTokenIndex];
                        if (sqlParserToken.TokenType != TSqlTokenType.Where) continue;

                        actions.Add(
                            builder =>
                            {
                                builder.Insert(
                                    whereClause.StartOffset + whereClause.FragmentLength, " ) ");
                                builder.Insert(
                                    sqlParserToken.Offset + sqlParserToken.Text.Length, " 0 = 1 AND ( ");
                            });
                    }
                }
            }
            actions.Reverse();
            var stringBuilder = new StringBuilder(queryText);
            foreach (var action in actions) action(stringBuilder);
            return stringBuilder.ToString();
        }

        private static Type GetQueryType(IQuery query)
        {
            return query.GetType();
        }

        private static TResult WithSqlConnection<TResult>(this string connectionString, Func<SqlConnection, TResult> func)
        {
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();

                return func(connection);
            }
        }

        private static TResult WithSqlCommand<TResult>(this SqlConnection connection, Func<SqlCommand, TResult> func)
        {
            using (var sqlCommand = new SqlCommand())
            {
                sqlCommand.Connection = connection;

                return func(sqlCommand);
            }
        }

        private const BindingFlags AllBindingFlags = BindingFlags.Default |
                                                     BindingFlags.IgnoreCase |
                                                     BindingFlags.DeclaredOnly |
                                                     BindingFlags.Instance |
                                                     BindingFlags.Static |
                                                     BindingFlags.Public |
                                                     BindingFlags.NonPublic |
                                                     BindingFlags.FlattenHierarchy |
                                                     BindingFlags.InvokeMethod |
                                                     BindingFlags.CreateInstance |
                                                     BindingFlags.GetField |
                                                     BindingFlags.SetField |
                                                     BindingFlags.GetProperty |
                                                     BindingFlags.SetProperty |
                                                     BindingFlags.PutDispProperty |
                                                     BindingFlags.PutRefDispProperty |
                                                     BindingFlags.ExactBinding |
                                                     BindingFlags.SuppressChangeType |
                                                     BindingFlags.OptionalParamBinding |
                                                     BindingFlags.IgnoreReturn;

        public static void CheckAllQueriesInAvailableAssemblies(
            string connectionString,
            QueryResultPropertyTypeChecker queryResultPropertyTypeChecker,
            ParamCreator paramCreator,
            Func<Type, bool> queryTypePredicate,
            Func<MethodInfo, bool> methodInfoPredicate,
            ChoiceInvocationGetter choiceInvocationGetter)
        {
            CheckAllQueries(
                connectionString,
                queryResultPropertyTypeChecker,
                paramCreator,
                queryTypePredicate,
                methodInfoPredicate,
                choiceInvocationGetter,
                GetAllTypes()
                );
        }

        public static void CheckAllQueries(
            string connectionString,
            QueryResultPropertyTypeChecker queryResultPropertyTypeChecker,
            ParamCreator paramCreator,
            Func<Type, bool> queryTypePredicate,
            Func<MethodInfo, bool> methodInfoPredicate,
            ChoiceInvocationGetter choiceInvocationGetter,
            IEnumerable<Type> types)
        {
            foreach (var type in types)
            {
                if (!QueryExtensions.QueryImplementationType.Equals(type)
                    && !QueryExtensions.NonQueryImplementationType.Equals(type))
                {
                    foreach (var @interface in type.GetInterfaces())
                    {
                        if (@interface.IsGenericTypeAndEquals(typeof(IQuery<>)))
                        {
                            if (queryTypePredicate(type))
                            {
                                Console.WriteLine(type);
                                foreach (var queryCreator in new ChoiceInvocationContext(choiceInvocationGetter).GetConstructorInvocations(type))
                                {
                                    InvokeCheckQuery(connectionString, queryResultPropertyTypeChecker, paramCreator, queryCreator, @interface.GetGenericArguments());
                                }
                            }
                        }
                        if (typeof(INonQuery).Equals(@interface))
                        {
                            if (queryTypePredicate(type))
                            {
                                Console.WriteLine(type);
                                foreach (var queryCreator in new ChoiceInvocationContext(choiceInvocationGetter).GetConstructorInvocations(type))
                                {
                                    InvokeCheckNonQuery(connectionString, paramCreator, queryCreator);
                                }
                            }
                        }
                    }
                }
                type.GetMethods(AllBindingFlags).ForEach(
                    methodInfo =>
                        {
                            if (methodInfoPredicate(methodInfo))
                            {
                                Action<MethodInfo> action =
                                    resolveMethodInfo =>
                                    CheckMethod(methodInfo, choiceInvocationGetter,
                                                queryCreator =>
                                                InvokeCheckQuery(connectionString, queryResultPropertyTypeChecker,
                                                                 paramCreator, queryCreator,
                                                                 resolveMethodInfo.GetGenericArguments()));
                                FindMethod(
                                    methodInfo,
                                    methodInfo.GetGenericArguments,
                                    action,
                                    resolveMethodInfo => CheckMethod(
                                        methodInfo, choiceInvocationGetter,
                                        queryCreator =>
                                        InvokeCheckNonQuery(connectionString, paramCreator, queryCreator)),
                                    action);
                            }
                        });
                type.GetConstructors(AllBindingFlags).ForEach(
                    methodInfo => FindMethod(
                        methodInfo,
                        () => new Type[] {},
                        resolveMethodInfo => ThrowQueryCheckException(toQueryMethodInfo, methodInfo),
                        resolveMethodInfo => ThrowQueryCheckException(toNonQueryMethodInfo, methodInfo),
                        resolveMethodInfo => ThrowQueryCheckException(toQueryRecordExampleMethodInfo, methodInfo)));                
            }
        }

        private static void CheckMethod(
            MethodInfo methodInfo,
            ChoiceInvocationGetter choiceInvocationGetter, 
            Action<QueryCreator> action)
        {
            if (!methodInfo.IsStatic)
            {
                throw new QueryCheckException(string.Format("Method is not static Method = '{0}', DeclaringType = '{1}'.", methodInfo, methodInfo.DeclaringType));
            }
            Console.WriteLine(methodInfo);
            foreach (var queryCreator in new ChoiceInvocationContext(choiceInvocationGetter)
                .GetStaticMethodInvocations(methodInfo))
            {
                action(queryCreator);
            }
        }

        private static void ThrowQueryCheckException(MethodInfo methodInfo, ConstructorInfo constructorInfo)
        {
            throw new QueryCheckException(
                string.Format("{0} method is called in a constructor of type '{1}'.",
                              methodInfo.Name, constructorInfo.DeclaringType));
        }

        private static void InvokeCheckNonQuery(string connectionString, ParamCreator paramCreator,
                                                QueryCreator queryCreator)
        {
            checkNonQueriesGenericMethodInfo.Invoke(null, new object[] {connectionString, paramCreator, queryCreator});
        }

        private static void InvokeCheckQuery(string connectionString,
                                             QueryResultPropertyTypeChecker queryResultPropertyTypeChecker, ParamCreator paramCreator,
                                             QueryCreator queryCreator, Type[] getGenericArguments)
        {
            checkQueryGenericMethodInfo.MakeGenericMethod(getGenericArguments)
                .Invoke(null, new object[] {connectionString, queryResultPropertyTypeChecker, paramCreator, queryCreator});
        }

        private static readonly MethodInfo toNonQueryMethodInfo = 
            Util.GetMethodInfo<Func<string, INonQuery>>(_ => _.ToNonQuery());

        private static readonly MethodInfo toQueryMethodInfo =
            Util.GetMethodInfo<Func<string, IQuery<object>>>(_ => _.ToQuery<object>()).GetGenericMethodDefinition();

        private static readonly MethodInfo toQueryRecordExampleMethodInfo =
            Util.GetMethodInfo<Func<string, object, IQuery<object>>>((x1, x2) => x1.ToQuery<object>(x2)).GetGenericMethodDefinition();

        private static readonly MethodInfo checkNonQueriesGenericMethodInfo =
            Util.GetMethodInfo<Action<string, ParamCreator, QueryCreator>>((x1, x2, x3) => CheckNonQuery(x1, x2, x3));

        private static void CheckNonQuery(string connectionString, ParamCreator paramCreator, QueryCreator queryCreator)
        {
            CheckNonQueries(connectionString, GetQueryFuncs<INonQuery>(queryCreator, paramCreator));
        }

        private static readonly MethodInfo checkQueryGenericMethodInfo =
            Util.GetMethodInfo<Action<string, QueryResultPropertyTypeChecker, ParamCreator, QueryCreator>>((x1, x2, x3, x4) => CheckQuery<object>(x1, x2, x3, x4)).GetGenericMethodDefinition();

        private static void CheckQuery<T>(string connectionString, QueryResultPropertyTypeChecker queryResultPropertyTypeChecker, ParamCreator paramCreator, QueryCreator queryCreator)
        {
            CheckQueries(connectionString, queryResultPropertyTypeChecker, GetQueryFuncs<IQuery<T>>(queryCreator, paramCreator));
        }

        private static Func<QueryContext, T> GetQueryFuncs<T>(QueryCreator queryCreator, ParamCreator paramCreator)
        {
            return queryContext => (T) queryCreator(
                new QueryCreatorContext(type => paramCreator(type, queryContext)));
        }

        private static IEnumerable<IEnumerable<TResult>> GetAllChoices<T, TResult>(
            IEnumerable<T> items,
            Func<T, IEnumerable<TResult>> choiceFunc)
        {
            return items.Aggregate(
                new IEnumerable<TResult>[] { },
                (seed, item) =>
                choiceFunc(item).SelectMany(
                    choiceItem => seed.Length == 0
                                          ? new[] { new[] { choiceItem } }
                                          : seed.Select(resultItem => resultItem.Concat(new[] { choiceItem }))
                    ).ToArray()
                );
        }

        private class ChoiceInvocationContext : IChoiceInvocationContext
        {
            private readonly ChoiceInvocationGetter choiceInvocationGetter;

            internal ChoiceInvocationContext(
                ChoiceInvocationGetter choiceInvocationGetter)
            {
                this.choiceInvocationGetter = choiceInvocationGetter;
            }

            public IEnumerable<QueryCreator> GetMethodBaseInvocations(MethodBase methodBase, Func<object[], object> invokeFunc)
            {
                var parameterInfos = methodBase.GetParameters();
                if (parameterInfos.Length == 0)
                {
                    return new QueryCreator[] { delegate { return invokeFunc(new object[] { }); } };
                }
                return GetAllChoices(parameterInfos, parameterInfo => GetChoiceInvocations(parameterInfo.ParameterType))
                    .Select<IEnumerable<QueryCreator>, QueryCreator>(
                        queryCreatorEnumerable => queryCreatorContext => invokeFunc(
                            queryCreatorEnumerable.Select(queryCreator => queryCreator(queryCreatorContext)).ToArray())
                    );
            }

            private IEnumerable<QueryCreator> GetChoiceInvocations(Type type)
            {
                if (type.IsGenericTypeAndEquals(typeof(IOptionalValue<>)))
                {
                    return new[] {asOptionalValueMethodInfo, nothingOptionalValueMethodInfo}.SelectMany(
                        genericMethodInfo => this.GetStaticMethodInvocations(genericMethodInfo.MakeGenericMethod(type.GetGenericArguments())));
                }
                else if (type.IsGenericTypeAndEquals(typeof(IParam<>)))
                {
                    return new QueryCreator[]
                           {
                               queryCreatorContext => queryCreatorContext.CreateParamFunc(type)
                           };
                }
                else if (typeof(bool).Equals(type))
                {
                    return new QueryCreator[] { delegate { return false; }, delegate { return true; } };
                }
                else
                {
                    var genericArguments = type.GetGenericArguments();
                    if (type.IsGenericTypeAndEquals(typeof(Func<>)) && genericArguments.Length == 1)
                    {
                        var genericArgument = genericArguments[0];
                        var genericMethod = getFuncQueryCreatorMethodInfo.MakeGenericMethod(genericArgument);
                        return GetChoiceInvocations(genericArgument)
                            .Select(queryCreator => (QueryCreator)genericMethod.Invoke(
                                null, new object[] { queryCreator }));
                    }
                    else if (type.IsEnum)
                    {
                        return type.GetEnumValues().Cast<object>()
                            .Select<object, QueryCreator>(enumItem => delegate { return enumItem; });
                    }
                    else if (type.IsInterface)
                    {
                        return GetImplementations(type).SelectMany(_ => this.GetConstructorInvocations(_));
                    }
                    else if (DynamicMaterializer.IsAnonymousType(type))
                    {
                        return this.GetConstructorInvocations(type);
                    }
                    else
                    {
                        return choiceInvocationGetter(type, this);
                    }
                }
            }
        }

        private static readonly MethodInfo getFuncQueryCreatorMethodInfo =
            Util.GetMethodInfo<Func<QueryCreator, QueryCreator>>(_ => GetFuncQueryCreator<object>(_)).GetGenericMethodDefinition();

        private static QueryCreator GetFuncQueryCreator<T>(QueryCreator queryCreator)
        {
            return context => Func.New(() => (T)queryCreator(context));
        }

        private static readonly MethodInfo asOptionalValueMethodInfo = 
            Util.GetMethodInfo<Func<object, IOptionalValue<object>>>(_ => _.AsOptionalValue()).GetGenericMethodDefinition();

        private static readonly MethodInfo nothingOptionalValueMethodInfo =
            Util.GetMethodInfo<Func<IOptionalValue<object>>>(() => OptionalValue.Nothing<object>()).GetGenericMethodDefinition();

        private static IEnumerable<Type> GetImplementations(Type interfaceType)
        {
            foreach (var type in GetAllTypes())
            {
                foreach (var @interface in type.GetInterfaces())
                {
                    if (interfaceType.IsGenericType)
                    {
                        if (@interface.IsGenericType)
                        {
                            if (interfaceType.GetGenericTypeDefinition().Equals(@interface.GetGenericTypeDefinition()))
                            {
                                var typeArguments = type.GetGenericArguments();
                                var dictionary = typeArguments.Select((type1, index) => new { type = type1, index })
                                    .ToDictionary(arg => arg.type, arg => arg.index);
                                var interfaceArguments = @interface.GetGenericArguments();
                                var inputInterfaceArguments = interfaceType.GetGenericArguments();
                                for (var index = 0; index < interfaceArguments.Length; index++)
                                {
                                    typeArguments[dictionary[interfaceArguments[index]]] = inputInterfaceArguments[index];
                                }
                                yield return type.MakeGenericType(typeArguments);
                            }
                        }
                    }
                    else
                    {
                        if (interfaceType.Equals(@interface))
                        {
                            yield return type;
                        }
                    }
                }
            }
        }

        private static IEnumerable<Type> GetAllTypes()
        {
            return GetAllAssemblies().SelectMany(assembly => assembly.GetTypes());
        }

        private static IEnumerable<Assembly> GetAllAssemblies()
        {
            var assemblies = new HashSet<Assembly>();
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                assemblies.AddExcludeGlobalAssemblyCache(assembly);
                LoadReferencedAssemblies(assembly, assemblies);
            }
            return assemblies;
        }

        private static bool AddExcludeGlobalAssemblyCache(this HashSet<Assembly> assemblies, Assembly assembly)
        {
            if (assembly.GlobalAssemblyCache) return false;
            return assemblies.Add(assembly);
        }

        private static void LoadReferencedAssemblies(Assembly assembly, HashSet<Assembly> assemblies)
        {
            foreach (var assemblyName in assembly.GetReferencedAssemblies())
            {
                var loadedAssembly = Assembly.Load(assemblyName.FullName);
                if (assemblies.AddExcludeGlobalAssemblyCache(loadedAssembly))
                {
                    LoadReferencedAssemblies(loadedAssembly, assemblies);
                }
            }
        }

        /// <summary>
        /// http://www.codeproject.com/KB/cs/sdilreader.aspx
        /// </summary>
        private static void FindMethod(MethodBase methodInfo, Func<Type[]> genericMethodArgumentsFunc, Action<MethodInfo> foundToQueryMethodAction, Action<MethodInfo> fountToNonQueryMethodAction, Action<MethodInfo> foundToQueryRecordExampleMethodAction)
        {
            var onResultAction = OptionalValue.Nothing<Action>();
            var methodBody = methodInfo.GetMethodBody();
            if (methodBody != null)
            {
                var ilAsByteArray = methodBody.GetILAsByteArray();
                var position = 0;
                var module = methodInfo.Module;
                while (position < ilAsByteArray.Length)
                {
                    OpCode opCode;
                    ushort value = ilAsByteArray[position++];
                    if (value == 0xfe)
                    {
                        value = ilAsByteArray[position++];
                        opCode = multiByteOpCodes[value];
                    }
                    else
                    {
                        opCode = singleByteOpCodes[value];
                    }
                    switch (opCode.OperandType)
                    {
                        case OperandType.InlineBrTarget:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineField:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineMethod:
                            var resolveMember = module.ResolveMember(
                                ReadInt32(ilAsByteArray, ref position),
                                methodInfo.DeclaringType.GetGenericArguments(),
                                genericMethodArgumentsFunc());
                            if (resolveMember.MemberType == MemberTypes.Method)
                            {
                                var resolveMethodInfo = (MethodInfo) resolveMember;
                                if (resolveMethodInfo.IsGenericMethod &&
                                    toQueryMethodInfo.Equals(resolveMethodInfo.GetGenericMethodDefinition()))
                                {
                                    SetOnResultAction(() => foundToQueryMethodAction(resolveMethodInfo),
                                                      ref onResultAction, methodInfo);
                                }
                                else if (resolveMethodInfo.IsGenericMethod &&
                                    toQueryRecordExampleMethodInfo.Equals(resolveMethodInfo.GetGenericMethodDefinition()))
                                {
                                    SetOnResultAction(() => foundToQueryRecordExampleMethodAction(resolveMethodInfo),
                                                      ref onResultAction, methodInfo);
                                }
                                else if (toNonQueryMethodInfo.Equals(resolveMethodInfo))
                                {
                                    SetOnResultAction(() => fountToNonQueryMethodAction(resolveMethodInfo),
                                                      ref onResultAction, methodInfo);
                                }
                            }
                            break;
                        case OperandType.InlineSig:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineTok:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineType:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineI:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineI8:
                            ReadInt64(ref position);
                            break;
                        case OperandType.InlineNone:
                            break;
                        case OperandType.InlineR:
                            ReadDouble(ref position);
                            break;
                        case OperandType.InlineString:
                            ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineSwitch:
                            var count1 = ReadInt32(ilAsByteArray, ref position);
                            for (var i = 0; i < count1; i++) ReadInt32(ilAsByteArray, ref position);
                            break;
                        case OperandType.InlineVar:
                            ReadUInt16(ref position);
                            break;
                        case OperandType.ShortInlineBrTarget:
                            ReadSByte(ref position);
                            break;
                        case OperandType.ShortInlineI:
                            ReadSByte(ref position);
                            break;
                        case OperandType.ShortInlineR:
                            ReadSingle(ref position);
                            break;
                        case OperandType.ShortInlineVar:
                            ReadByte(ref position);
                            break;
                        default:
                            throw new InvalidOperationException();
                    }
                }
            }
            onResultAction.ProcessValue(value => value());
        }

        private static void SetOnResultAction(Action action, ref IOptionalValue<Action> onResultAction, MethodBase methodInfo)
        {
            if (onResultAction.HasValue)
            {
                throw new QueryCheckException(
                    string.Format(
                        "Method '{0}' contains more than one call of {1} or {2} or {3} methods.",
                        methodInfo, toQueryMethodInfo.Name, toQueryRecordExampleMethodInfo.Name, toNonQueryMethodInfo.Name));
            }
            onResultAction = action.AsOptionalValue();
        }

        static QueryChecker()
        {
            singleByteOpCodes = new OpCode[0x100];
            multiByteOpCodes = new OpCode[0x100];
            foreach (var fieldInfo in typeof(OpCodes).GetFields())
            {
                if (fieldInfo.FieldType == typeof(OpCode))
                {
                    var opCode = (OpCode)fieldInfo.GetValue(null);
                    var value = (ushort)opCode.Value;
                    if (value < 0x100)
                    {
                        singleByteOpCodes[value] = opCode;
                    }
                    else
                    {
                        if ((value & 0xff00) != 0xfe00)
                        {
                            throw new ApplicationException("Invalid OpCode.");
                        }
                        multiByteOpCodes[value & 0xff] = opCode;
                    }
                }
            }            
        }

        private static readonly OpCode[] multiByteOpCodes;
        private static readonly OpCode[] singleByteOpCodes;

        private static void ReadUInt16(ref int position)
        {
            position += 2;
        }

        private static int ReadInt32(byte[] bytes, ref int position)
        {
            return bytes[position++] | bytes[position++] << 8 | bytes[position++] << 0x10 | bytes[position++] << 0x18;
        }

        private static void ReadInt64(ref int position)
        {
            position += 8;
        }

        private static void ReadDouble(ref int position)
        {
            position += 8;
        }

        private static void ReadSByte(ref int position)
        {
            position++;
        }

        private static void ReadByte(ref int position)
        {
            position++;
        }

        private static void ReadSingle(ref int position)
        {
            position += 4;
        }
    }
}